#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         Common.py
#
# Copyright:    Copyright (c) 2004, Faulkner Technologies
#
# Author:       Brian Oliver
#
# Description:  Defines common utility methods and constants for use
#               within the KnowledgeEngine
#
# See Also:     PropertyRefleciton, PropertyDefinition, PropertyAccess 
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
# (none)

#####################################################################
## Zope Library Imports
from App.FindHomes import ppath
from DateTime import DateTime
 
#####################################################################
## Python Library Imports
from time import time
from calendar import monthrange
from random import random
from types import TypeType, ClassType
import string, os

#####################################################################

#boolean literal declarations
true = 1 == 1       #the python value for true
false = not true           
undefined = -1      #an undefined value for tristate values (not true and not false)

#####################################################################

def getProductDirectory():
    """
        Returns the product directory for KnowledgeEngine
    """
    ke_directory = None
    for item in ppath:
        directory = item + os.sep + "KnowledgeEngine"
        if os.path.exists(directory):
            ke_directory = directory
    
    return ke_directory

#####################################################################

def generateIdentity(identitytype='abstract'):
    """
        Returns a unique number as a string based on the current
        time and a random number.  This method is used to generate
        unique identities for GenericObjects
    """
    return identitytype + "0" + str(int(time())) + str(random())[2:]

#####################################################################

def toSQL(str):
    """
      Return an SQL safe marked up string of the provided string.
    """
    
    result = str
    result = string.replace(result, "\\", r"\\")
    result = string.replace(result, "\"", r"\"")
    result = string.replace(result, "\'", r"\'")
    result = string.replace(result, "\r", r"\r")
    result = string.replace(result, "\n", r"\n")
    result = string.replace(result, "\t", r"\t")
    return result

#####################################################################

def escape(str):
    """
      Return an escaped string of the provided string.
    """
    result = str
    result = string.replace(result, "\\", "\\\\")
    result = string.replace(result, "\"", "\\\"")
    result = string.replace(result, "\'", "\\\'")
    result = string.replace(result, "\r", "\\r")
    result = string.replace(result, "\n", "\\n")
    result = string.replace(result, "\t", "\\t")
    return result

#####################################################################

def asJavaScript(str):
    """
      Return a JavaScript safe marked up string of the provided string.
    """
    result = str
    result = string.replace(result, "\"", "\\\"")
    result = string.replace(result, "\'", "\\\'")
    result = string.replace(result, "\r", "\\r")
    result = string.replace(result, "\n", "\\n")
    result = string.replace(result, "\t", "\\t")
    return result

#####################################################################

def toHTML(str):
    """
      Return an SQL safe marked up string of the provided string.
    """
    
    result = str
    result = string.replace(result, "\n", "<br>")
    result = string.replace(result, "&", "&amp;")
    result = string.replace(result, "\"", "&quot;")
    result = string.replace(result, "\'", "'")
    result = string.replace(result, "<", "&lt;")
    result = string.replace(result, ">", "&gt;")
    return result
    
#####################################################################

def iif(guard, trueValue, falseValue):
    """
        A conditional iff statement
    """
    
    if guard:
        return trueValue
    else:
        return falseValue
        
#####################################################################

def monthAdd(dateTime, nrMonths, preferredDay = None):
    """
        Adds the specified number of months to the provided date 
        and returns a new date.
        
        Specify the 'preferredDay' of the Month (1-31), if
        you want the method to attempt to return the final month
        using that day - if the day is available.
    """

    #determine the preferred day of the month (if specified)
    if preferredDay is None:
        preferredDay = dateTime.day()

    #determine the time of the current day
    hour = dateTime.hour()
    minute = dateTime.minute()
    second = dateTime.second()
    timezone = dateTime.timezone()

    #determine the next/previous month
    monthDelta = iif(nrMonths < 0, -1, 1)

    #keep adding/subtracting months until we're done
    while nrMonths != 0:            
        #is the next month in the next year?
        if dateTime.month() + monthDelta > 12:
            (year, month) = (dateTime.year() + 1, 1)           

        #is the next month in the previous year?
        elif dateTime.month() + monthDelta < 1:
            (year, month) = (dateTime.year() - 1, 12)        

        #the next/previous month in the current year
        else:
            (year, month) = (dateTime.year(), dateTime.month() + monthDelta)

        #get the number of days in the next/previous month
        (fdom, daysInMonth) = monthrange(year, month)

        #are there enough days in the next/previous month for the preferred day
        if preferredDay <= daysInMonth:
            dateTime = DateTime(year, month, preferredDay, hour, minute, second, timezone)
        else:
            dateTime = DateTime(year, month, daysInMonth, hour, minute, second, timezone)

        #one less month to add
        nrMonths = nrMonths - monthDelta

    return dateTime                
        
#####################################################################

# The built-in 'isinstance()' and 'issubclass()' won't work on
# ExtensionClasses, so you have to use the versions supplied here.
# (But those versions work fine on regular instances and classes too,
# so you should *always* use them.)
#
# REFERENCE: http://www.amk.ca/zodb/guide/node18.html

def issubclass(class1, class2):
    """A version of 'issubclass' that works with extension classes
    as well as regular Python classes.
    """

    # Both class objects are regular Python classes, so use the
    # built-in 'issubclass()'.
    if type(class1) is ClassType and type(class2) is ClassType:
        return __builtin__.issubclass(class1, class2)

    # Both so-called class objects have a '__bases__' attribute: ie.,
    # they aren't regular Python classes, but they sure look like them.
    # Assume they are extension classes and reimplement what the builtin
    # 'issubclass()' does behind the scenes.
    elif hasattr(class1, '__bases__') and hasattr(class2, '__bases__'):
        # XXX it appears that "ec.__class__ is type(ec)" for an
        # extension class 'ec': could we/should we use this as an
        # additional check for extension classes?

        # Breadth-first traversal of class1's superclass tree.  Order
        # doesn't matter because we're just looking for a "yes/no"
        # answer from the tree; if we were trying to resolve a name,
        # order would be important!
        stack = [class1]
        while stack:
            if stack[0] is class2:
                return 1
            stack.extend(list(stack[0].__bases__))
            del stack[0]
        else:
            return 0

    # Not a regular class, not an extension class: blow up for consistency
    # with builtin 'issubclass()"
    else:
        raise TypeError, "arguments must be class or ExtensionClass objects"

#####################################################################

def isinstance(object, klass):
    """A version of 'isinstance' that works with extension classes
    as well as regular Python classes."""

    if type(klass) is TypeType:
        return __builtin__.isinstance(object, klass)
    elif hasattr(object, '__class__'):
        return issubclass(object.__class__, klass)
    else:
        return 0

#####################################################################
